package com.yc.springmvc;

import com.google.gson.Gson;
import com.yc.springmvc.controller.IndexController;
import com.yc.springmvc.controller.MyIndexAction;
import com.yc.springmvc.controller.ParamDo;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

public class MyDispatchServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    Map<String,Object> ioc = new HashMap<>();
    {
        ioc.put("cotroller", new IndexController());
        ioc.put("action", new MyIndexAction());
        ioc.put("do", new ParamDo());
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.service(req, resp);
        Handler handler = handlerMapping(req);
        if(handler==null){
            // 404 错误
            defaultServletHandler(req,resp);
            return;
        }
        Object res = handlerAdapter(handler, req, resp);
        System.out.println(" ***************************    res = " + res);

        // 1. 405
        // 2. 对 res 对象的返回 => 1) json, 2) 页面跳转
        if(returnJson(handler)){
            // json
            Gson gson = new Gson();
            String json = gson.toJson(res);
            resp.setContentType("application/json;charset=utf-8");
            resp.getWriter().append(json);
            resp.flushBuffer();
        } else if(returnHtml(handler)){
            // html
            String path = null;
            if(res instanceof ModelAndView){
                path = ((ModelAndView) res).getViewName();
            } else if(res instanceof String){
                path = (String) res;
            }
            // 只处理 响应重定向
            String url = path.substring("redirect:".length());
            resp.sendRedirect(url);
            resp.flushBuffer();
        } else {
            // TODO 其他可能返回结果
        }

    }

    private void defaultServletHandler(HttpServletRequest req, HttpServletResponse resp) {
        String path = req.getServletPath();
        // /A.html
        URL url = this.getClass().getClassLoader().getResource("");
        System.out.println("url.getPath() = " + url.getPath());
        // URL 地址解码
        String filePath = url.getPath();
        filePath = URLDecoder.decode(filePath);
        System.out.println("filePath = " + filePath);
        // 拼接文件路径
        File dir = new File(filePath.substring(1));
        File webRoot = dir.getParentFile().getParentFile();
        File file = new File(webRoot, path);
        System.out.println("file = " + file);
        if(path.endsWith(".html")){
            resp.setContentType("text/html;charset=utf-8");
            try(FileInputStream fis = new FileInputStream(file);
                OutputStream out = resp.getOutputStream()){
                byte[] bytes = new byte[1024];
                int count;
                while((count=fis.read(bytes))>0){
                    out.write(bytes,0,count);
                }
            } catch (IOException e) {
                throw new RuntimeException("静态资源文件输出错误!",e);
            }
        } else if(path.endsWith(".css")){
            // css
        } else if(path.endsWith(".js")){
            // js
        } else {
            // 其他文件类型
        }
    }

    // 获取处理器(控制器+方法)
    public Handler handlerMapping(HttpServletRequest req){
        String path = req.getServletPath();
        // 查找控制器对象
        for (Object controller : ioc.values()) {
            // 获取类上的 requestmapping 注解
            RequestMapping requestMapping =
                    controller.getClass().getAnnotation(RequestMapping.class);
            String annoPath = null;
            if(requestMapping!=null){
                annoPath = requestMapping.value().length>0 ?
                        requestMapping.value()[0]:
                        requestMapping.path()[0];
                if (annoPath.startsWith("/")==false) {
                    // 补 /
                    annoPath = "/" + annoPath;
                }
            }
            for (Method method : controller.getClass().getDeclaredMethods()) {
                // 定义一个新的的变量, 记录当前方法的地址
                String controllerPath = annoPath == null? "": annoPath;

                RequestMapping requestMapping1 =
                        method.getAnnotation(RequestMapping.class);
                // GetMapping PostMapping...
                if(requestMapping1 == null){
                    continue;
                }
                String methodPath = requestMapping1.value().length>0 ?
                        requestMapping1.value()[0]:
                        requestMapping1.path()[0];
                if (methodPath.startsWith("/")==false) {
                    // 补 /
                    methodPath = "/" + methodPath;
                }
                controllerPath += methodPath;
                if(path.equals(controllerPath)){
                    System.out.println("--------------- path = " + path);
                    System.out.println("--------------- method = " + method);
                    return new Handler(controller, method);
                }
            }

        }
        return null;
    }

    // 执行处理器
    public Object handlerAdapter(Handler handler,
                                 HttpServletRequest req,
                                 HttpServletResponse resp){
        // 解析参数
//        Parameter[] parameters = handler.getMethod().getParameters();
//        for (Parameter parameter : parameters) {
//            System.out.println("=========== parameter = " + parameter.getName());
//        }
        List<String> names = getParameterNames(handler.getMethod());
        // 方法参数值数组
        List<Object> values = new ArrayList<>();
        for (int i = 0; i < handler.getMethod().getParameters().length; i++) {
            String name = names.get(i);
            String svalue = req.getParameter(name);
            Parameter parameter = handler.getMethod().getParameters()[i];
            // 转型
            if(parameter.getType().equals(String.class)){
                values.add(svalue);  // 字符串
            } else if(parameter.getType().equals(Integer.class) ||
                    parameter.getType().equals(int.class)){
                values.add(Integer.valueOf(svalue));  // int
            } else if(parameter.getType().equals(Long.class) ||
                    parameter.getType().equals(long.class)){
                // TODO long
            } else if(parameter.getType().isAssignableFrom(HttpServletRequest.class)){
                values.add(req);
            } else if(parameter.getType().isAssignableFrom(HttpServletResponse.class)){
                values.add(resp);
            } else if(parameter.getType().isAssignableFrom(HttpSession.class)){
                values.add(req.getSession());
            } else {
                if (parameter.getAnnotation(RequestBody.class)!=null) {
                    // 参数类型
                    Class<?> type = parameter.getType();
                    // body内容
                    try {
                        BufferedReader reader = req.getReader();
                        char[] chars = new char[1024];
                        int count;
                        StringBuffer sb = new StringBuffer();
                        while((count=reader.read(chars))>0){
                            sb.append(chars, 0, count);
                        }
                        String json = sb.toString();
                        System.out.println("json = " + json);
                        Gson gson = new Gson();
                        Object object = gson.fromJson(json, type);
                        System.out.println("object = " + object);
                        values.add(object);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 实体对象
                    Object object = createObject(parameter, req);
                    values.add(object);
                }
            }
        }
        handler.getMethod().setAccessible(true);
        try {
            return handler.getMethod().invoke(handler.getController(), values.toArray());
        } catch (Exception e) {
            throw new RuntimeException("控制器方法执行错误!", e);
        }
    }

    private Object createObject(Parameter parameter, HttpServletRequest req) {
        Object obj = null;
        try {
            obj = parameter.getType().newInstance();
            setFiledValue(obj, req, null);
        } catch (Exception e) {
            throw new RuntimeException("自动装箱错误!", e);
        }
        return obj;
    }

    // 判断请求参数中是否包含  fieldPath 开头的参数
    boolean containSubField(HttpServletRequest req, String fieldPath){
        Enumeration<String> names = req.getParameterNames();
        while(names.hasMoreElements()){
            String name = names.nextElement();
            String regex = fieldPath+"\\.\\w+";
            System.out.println("--------regex = " + regex + "  name = " + name);
            if(name.matches(regex)){
                return true;
            }
        }
        return false;
    }

    void setFiledValue(Object object, HttpServletRequest req, String fieldPath)
            throws IllegalAccessException, InstantiationException {
        // field => name,age...
        for (Field field : object.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            String paramName = field.getName();
            if(fieldPath!=null){
                paramName = fieldPath + "." + paramName;
            }
            System.out.println("paramName = " + paramName);
            String value = req.getParameter(paramName);

            if(value == null && containSubField(req,paramName)){
                // 实体对象
                Object subObject = field.getType().newInstance();
                // 递归调用
                setFiledValue(subObject, req, paramName);
                field.set(object, subObject);
                continue;
            }

            if(field.getType().isAssignableFrom(String.class)){
                field.set(object, value);
            } else if(field.getType().isAssignableFrom(Integer.class)||
                    field.getType().isAssignableFrom(int.class)){
                field.set(object, Integer.valueOf(value));
            } else if(field.getType().isAssignableFrom(Boolean.class)||
                    field.getType().isAssignableFrom(boolean.class)){
                field.set(object, Boolean.valueOf(value));
            } else if(field.getType().isAssignableFrom(Long.class)||
                    field.getType().isAssignableFrom(long.class)){
                // TODO long
            } else if(field.getType().isAssignableFrom(List.class)){
                // TODO list
            } else {
                // 其他类型
            }
        }
    }



    public boolean returnJson(Handler handler){
        RestController restController =
                handler.getController().getClass().getAnnotation(RestController.class);
        Class<?> returnType = handler.getMethod().getReturnType();

        Controller controller =
                handler.getController().getClass().getAnnotation(Controller.class);
        ResponseBody responseBody = handler.getMethod().getAnnotation(ResponseBody.class);

        return restController!=null && ! returnType.isAssignableFrom(ModelAndView.class)
                || controller !=null && responseBody !=null;
    }

    public boolean returnHtml(Handler handler){
        RestController restController =
                handler.getController().getClass().getAnnotation(RestController.class);
        Class<?> returnType = handler.getMethod().getReturnType();

        Controller controller =
                handler.getController().getClass().getAnnotation(Controller.class);
        ResponseBody responseBody = handler.getMethod().getAnnotation(ResponseBody.class);

        return restController!=null && returnType.isAssignableFrom(ModelAndView.class)
                || controller !=null && responseBody ==null;
    }

    @Data
    @AllArgsConstructor
    class Handler{
        Object controller;
        Method method;
    }

    public List<String> getParameterNames(Method method) {
        LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer =
                new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        List<String> names = new ArrayList<>();
        if (parameterNames != null) {
            for (String param : parameterNames) {
                names.add(param);
            }
        }
        return names;
    }
}
